#include "messageshandler.h"
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>

bool Process_::operator ==(const struct Process_ &p)const{
    return this->processId == p.processId;
}

bool LockDesc_::operator ==(const struct LockDesc_ &p)const{
    return this->processId == p.processId
            && this->resourceId == p.resourceId
            && this->type == p.type;
}

MessagesHandler::MessagesHandler()
{
    clock_gettime(CLOCK_MONOTONIC, &lastTime_);
}


void MessagesHandler::handleMsg(RequestMsg &msg){
    fprintf(stdout, "Obsługa wiadomości typu %d\n", msg.type);
    handleTimeout();
    switch(msg.type){
    case REQ_OPEN:
        handleOpen(msg);
        break;
    case REQ_CLOSE:
        handleClose(msg);
        break;
    case REQ_ADD_RES:
        handleAddResource(msg);
        break;
    case REQ_DEL_RES:
        handleDelResource(msg);
        break;
    case REQ_LOCK:
        handleLock(msg);
        break;
    case REQ_TRYLOCK:
        handleTryLock(msg);
        break;
    case REQ_UNLOCK:
        handleUnlock(msg);
        break;
    default:
        handleUnsupportedMsg(msg);
        break;
    }

}

long timespecDiff(struct timespec *timeA, struct timespec *timeB)
{
  return ((timeA->tv_sec * 1000000000) + timeA->tv_nsec) -
           ((timeB->tv_sec * 1000000000) + timeB->tv_nsec);
}

void MessagesHandler::handleDelResource(RequestMsg &msg){
    Process* p = getProcess(msg.processId);
    if(p==NULL){
        handleUnknownProcessId(msg.processId);
        return;
    }
    if(!isValidResourceId(msg.resourceId, p)){
        sendResponse(p->processId, STATUS_NOT_EXISTS);
        return;
    }
    delResource(msg.resourceId, p);
    removeProcessFromAllQueues(p, &msg.resourceId);
    sendResponse(p, STATUS_OK);
}

void MessagesHandler::delResource(RescourceId id, Process *p){
    list<RescourceId>::iterator it;
    p->allResources.remove(id);
    removeProcessFromAllQueues(p, &id);
}

void MessagesHandler::handleTimeout(){
    struct timespec currentTime;
    clock_gettime(CLOCK_MONOTONIC, &currentTime);
    long diffSinceLastTimeInNanoSec = timespecDiff(&currentTime, &lastTime_);
    clock_gettime(CLOCK_MONOTONIC, &lastTime_);

    list<LockDesc>::iterator it;
    for( it = waitingLocksWithTimeout_.begin(); it != waitingLocksWithTimeout_.end(); it++){
        // odliczamy czas który upłynął
        (*it).timeout -= diffSinceLastTimeInNanoSec;
        if((*it).timeout <= 0){
            // upłynął timeout oczekiwania na blokadę
            fprintf(stderr, "upłynal timeout dla procesu %d\n", (*it).processId);
            sendResponse((*it).processId, STATUS_TIMEOUT);
            waitingLocksWithTimeout_.erase(it++);
        }
    }
}

void MessagesHandler::handleOpen(RequestMsg &msg){
    static int nextProcessId = 0;
    Process p;
    p.fifoName = msg.fifoName;

    if(isFifoOpened(p.fifoName)){
        fprintf(stderr, "Nie można ponownie otworzyć FIFO %s\n", p.fifoName);

        return;
    }

    if ((p.fd = open(p.fifoName, O_WRONLY)) == -1){
        fprintf(stderr, "Nie można otworzyć FIFO %s: %s\n", p.fifoName, strerror(errno));
        return;
    }
    p.processId = nextProcessId++;
    fprintf(stdout, "Otworzyłem FIFO %s dla id=%d\n", p.fifoName, p.processId);
    processes_.push_back(p);
    sendResponse(&p, STATUS_OK);
}

void MessagesHandler::handleClose(RequestMsg &msg){
    Process *p = getProcess(msg.processId);
    if(p!= NULL){
        fprintf(stdout, "Usunięto process o id = %d\n", p->processId);
        removeProcessFromAllQueues(p);
        processes_.remove(*p);
        sendResponse(p, STATUS_OK);
        close(p->fd);
        //TODO trzeba usunac niezwolnione zasoby
    }else{
        handleUnknownProcessId(msg.processId);
    }
}

void MessagesHandler::handleAddResource(RequestMsg &msg){
    Process *p = getProcess(msg.processId);
    if(p == NULL){
        handleUnknownProcessId(msg.processId);
        return;
    }
    if(isValidResourceId(msg.resourceId, p)){
        // process ma już ten zasób
        fprintf(stdout, "Procesu o id = %d ma juz zasob\n", msg.processId);
        sendResponse(msg.processId, STATUS_EXISTS);
        return;
    }
    if(!isValidResourceId(msg.resourceId)){
        // pierwsze odwołanie do zasobu
        allResources_.push_back(msg.resourceId);
    }
    p->allResources.push_back(msg.resourceId);
    sendResponse(msg.processId, STATUS_OK);
    fprintf(stdout, "Dodano zasob %d\n", msg.resourceId);
}

bool MessagesHandler::isValidResourceId(const RescourceId resourceId, Process* subscriber)const{
    list<RescourceId>::const_iterator it;
    const list<RescourceId> *resourcesList = NULL;
    if(subscriber == NULL){
        resourcesList = &allResources_;
    }else{
        resourcesList = &subscriber->allResources;
    }
    for( it = resourcesList->begin(); it != resourcesList->end(); it++){
        int res = *it;
        if(res == resourceId){
            return true;
        }
    }
    return false;
}

bool MessagesHandler::isValidLockType(const LockType type)const{
    return 0 <= type && type <= 4;
}

void MessagesHandler::handleTryLock(RequestMsg &msg){
    Process* p = getProcess(msg.processId);
    if(p == NULL){
        handleUnknownProcessId(msg.processId);
        return;
    }
    if(!isValidResourceId(msg.resourceId, p)){
        sendResponse(p, STATUS_NOT_EXISTS);
        return;
    }
    if(!isValidLockType(msg.lockType)){
        sendResponse(p, STATUS_ERR);
        return;
    }
    if(canLock(msg.resourceId, msg.lockType)){
        sendResponse(p->processId, STATUS_OK);
    }else{
        sendResponse(p->processId, STATUS_EXISTS);
    }
}

void MessagesHandler::handleLock(RequestMsg& msg){
    Process* p = getProcess(msg.processId);
    if(p == NULL){
        handleUnknownProcessId(msg.processId);
        return;
    }
    if(!isValidResourceId(msg.resourceId, p)){
        sendResponse(p, STATUS_NOT_EXISTS);
        return;
    }
    if(!isValidLockType(msg.lockType)){
        sendResponse(p, STATUS_ERR);
        return;
    }
    if(canLock(msg.resourceId, msg.lockType)){
        LockDesc l = buildLockDesc(msg);
        l.timeout = -1;
        currentLocks_[msg.resourceId].push_back(l);
        sendResponse(p, STATUS_OK);
        return;
    }else{
        const long timeout = msg.timeout;
        if(timeout == -1){
            // bez blokowania
            sendResponse(p, STATUS_EXISTS);
            return;
        }else{
            // czeka na zwolnienie zasobu
            LockDesc l= buildLockDesc(msg);
            if(timeout != 0){
                // podano timeout
                l.timeout = msg.timeout;
                waitingLocksWithTimeout_.push_back(l);
                fprintf(stdout, "Dodaje locka do listy oczekajacych %ld\n", l.timeout);
            }
            waitingLocks_[msg.resourceId].push_back(l);
            return;
        }
    }
}

LockDesc MessagesHandler::buildLockDesc(RequestMsg &msg){
    LockDesc l;
    l.processId = msg.processId;
    l.resourceId = msg.resourceId;
    l.timeout = msg.timeout;
    l.type = msg.lockType;
    return l;
}

void MessagesHandler::handleUnlock(RequestMsg& msg){
    Process* p = getProcess(msg.processId);
    if(p == NULL){
        handleUnknownProcessId(msg.processId);
        return;
    }
    if(!isValidResourceId(msg.resourceId, p)){
        sendResponse(p, STATUS_NOT_EXISTS);
        return;
    }

    {
        bool unlocked = false;
        //sprawdzamy czy zasob jest zablokowany przez dany proces
        list<LockDesc>& currentLocks = currentLocks_[msg.resourceId];
        list<LockDesc>::iterator it;
        for( it = currentLocks.begin(); it != currentLocks.end(); it++){
            LockDesc &l = *it;
            if(l.processId == msg.processId){
                currentLocks.erase(it++);
                unlocked = true;
                break;
            }
        }
        if(unlocked){
            //zasób był zablokowany
            sendResponse(p->processId, STATUS_OK);
        }else{
            //zasób nie był zablokowany
            sendResponse(p->processId, STATUS_NOT_EXISTS);
        }
    }
    //sprawdzamy kolejkę oczekujących
    parseWaiting(msg.resourceId);
}


void MessagesHandler::handleUnsupportedMsg(RequestMsg &msg){
    fprintf(stderr, "Błędny typ wiadomości %d od procesu %d\n", msg.type, msg.processId);
}

void MessagesHandler::handleUnknownProcessId(ProcessId id){
    fprintf(stdout, "Brak procesu o id = %d\n", id);
}

bool MessagesHandler::isFifoOpened(const char *fifoName)const{
    list<Process>::const_iterator it;
    for( it = processes_.begin(); it != processes_.end(); it++)
        if(strcmp((*it).fifoName, fifoName) == 0){
            return true;
        }
    return false;
}

void MessagesHandler::sendResponse(const ProcessId processId, const int status)const{
    const Process *p = getProcessC(processId);
    sendResponse(p, status);
    return;
}

bool MessagesHandler::canLock(const RescourceId id, const LockType type)const{
    list<LockDesc>& currentLocks = currentLocks_[id];
    list<LockDesc>::iterator it;
    for( it = currentLocks.begin(); it != currentLocks.end(); it++){
        LockDesc &l = *it;
        if(!locksMatrix_.canLock(l.type, type)){
            fprintf(stdout, "canLock: zasob %d jest zajety przez %d", l.resourceId, l.processId);
            return false;
        }
    }
    return true;
}

void MessagesHandler::parseWaiting(RescourceId id){
    //szukamy na liscie oczekujacych procesu który może założyć blokadę;
    list<LockDesc>& waitingLocks = waitingLocks_[id];
    list<LockDesc>::iterator it;
    for( it = waitingLocks.begin(); it != waitingLocks.end(); it++){
        LockDesc &l = *it;
        if(canLock(l.resourceId, l.type)){
            LockDesc newLock(l);
            waitingLocks.erase(it++);
            newLock.timeout = -1;
            currentLocks_[newLock.resourceId].push_back(newLock);
            sendResponse(newLock.processId, STATUS_OK);
        }else{
            return;
        }
    }
}

void MessagesHandler::sendResponse(const Process *p, const int status)const{
    assert(p != NULL);
    ResponseMsg outMsg;
    outMsg.processId = p->processId;
    outMsg.status = status;
    if(writeResonse(p->fd, &outMsg)){
        fprintf(stderr, "Nie można wysłać odpowiedzi do p. id = %d", p->processId);
    }
    return;
}


Process* MessagesHandler::getProcess(const ProcessId processId){
    const Process* p = getProcessC(processId);
    return const_cast<Process*>(p);
}

const Process* MessagesHandler::getProcessC(const ProcessId processId)const{
    list<Process>::const_iterator it;
    for( it = processes_.begin(); it != processes_.end(); it++){
        const Process* p = &(*it);
        if(p->processId == processId){
            return p;
        }
    }
    return NULL;
}

long MessagesHandler::getTimeout(){
    long newTimeout = 0;
    list<LockDesc>::iterator it;
    list<LockDesc>::iterator begin = waitingLocksWithTimeout_.begin();
    for( it = begin; it != waitingLocksWithTimeout_.end(); it++){
        // obliczamy najkrótszy timeout z wszystkich blokad
        // aby można było go obsłużyć gdy nadejdzie na to pora
        if(it == begin){
            newTimeout = (*it).timeout;
        }else{
            newTimeout = min(newTimeout, (*it).timeout);
        }
    }
    fprintf(stdout, "Ustawiam timeout na %ld\n", newTimeout);
    return newTimeout;
}

void MessagesHandler::removeProcessFromAllQueues(Process* p, RescourceId* id){
    list<RescourceId>::iterator resIt;
    for(resIt = p->allResources.begin() ; resIt != p->allResources.end(); resIt++){
        //pomijamy zasoby które mają inny identyfikator
        if(id == NULL || (*resIt) == *id){
            if(waitingLocks_.count(*resIt) > 0){
                //przegladamy oczekujace blokady
                list<LockDesc>& resourceLocks = waitingLocks_[*resIt];
                list<LockDesc>::iterator resLocksIt;
                for(resLocksIt = resourceLocks.begin(); resLocksIt != resourceLocks.end(); resLocksIt++){
                    // usuwamy wszystkie blokady nalezace do procesu p
                    if(p->processId == (*resLocksIt).processId){
                        resourceLocks.erase(resLocksIt++);
                    }
                }
            }

            if(currentLocks_.count(*resIt) > 0){
                //przeglądamy założone blokady na zasobie resIt
                list<LockDesc>& resourceLocks = currentLocks_[*resIt];
                list<LockDesc>::iterator resLocksIt;
                for(resLocksIt = resourceLocks.begin(); resLocksIt != resourceLocks.end(); resLocksIt++){
                    // usuwamy wszystkie blokady nalezace do procesu p
                    if(p->processId == (*resLocksIt).processId){
                        RescourceId resId = (*resLocksIt).resourceId;
                        resourceLocks.erase(resLocksIt++);
                        //sprawdzamy czy mozemy komus przyznac zasób
                        parseWaiting(resId);
                    }
                }
            }
        }
    }
    //przegladamy liste blokad z timeoutem

    list<LockDesc>::iterator lockIt;
    for(lockIt = waitingLocksWithTimeout_.begin(); lockIt != waitingLocksWithTimeout_.end(); lockIt++){
        // usuwamy wszystkie blokady nalezace do procesu p i mają odpowiednie ID
        if(p->processId == (*lockIt).processId && (id == NULL || *id == (*lockIt).resourceId)){
            waitingLocksWithTimeout_.erase(lockIt++);
        }
    }
}
